﻿using System.Collections.Immutable;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

namespace MetadataDesignerLibrary
{
    public class TypeDefinitionRule : ILRule<TypeDefinitionHandle>
    {
        [ILInfo(true)]
        public string Namespace { get; }

        [ILInfo(true)]
        public string Name { get; }

        [ILInfo(true)]
        public EntityHandle BaseType { get; }

        [ILInfo]
        public TypeAttributes Attributes { get; }

        [ILInfo]
        public bool IsNested { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandle)]
        public NamespaceDefinitionHandle NamespaceDefinition { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public CustomAttributeHandleCollection CustomAttributes { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public DeclarativeSecurityAttributeHandleCollection DeclarativeSecurityAttributes { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandle)]
        public TypeDefinitionHandle DeclaringType { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public EventDefinitionHandleCollection Events { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public FieldDefinitionHandleCollection Fields { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public GenericParameterHandleCollection GenericParameters { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public InterfaceImplementationHandleCollection InterfaceImplementations { get; }

        public TypeLayout Layout { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public MethodImplementationHandleCollection MethodImplementations { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public MethodDefinitionHandleCollection Methods { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public ImmutableArray<TypeDefinitionHandle> NestedTypes { get; }

        [ILInfo(ILInfoParseMethod.AsEntityHandleCollection)]
        public PropertyDefinitionHandleCollection Properties { get; }

        public TypeDefinitionRule(
            TypeAttributes attributes,
            string @namespace,
            string name,
            EntityHandle baseType)
            : base()
        {
            Attributes = attributes;
            Namespace = @namespace;
            Name = name;
            BaseType = baseType;
        }

        public TypeDefinitionRule(MetadataReader reader, TypeDefinitionHandle typeDefinitionHandle)
            : base(reader)
        {
            TypeDefinition typeDefinition = reader.GetTypeDefinition(typeDefinitionHandle);
            Attributes = typeDefinition.Attributes;
            BaseType = typeDefinition.BaseType;
            IsNested = typeDefinition.IsNested;
            Name = reader.GetString(typeDefinition.Name);
            Namespace = reader.GetString(typeDefinition.Namespace);
            NamespaceDefinition = typeDefinition.NamespaceDefinition;
            CustomAttributes = typeDefinition.GetCustomAttributes();
            DeclarativeSecurityAttributes = typeDefinition.GetDeclarativeSecurityAttributes();
            DeclaringType = typeDefinition.GetDeclaringType();
            Events = typeDefinition.GetEvents();
            Fields = typeDefinition.GetFields();
            GenericParameters = typeDefinition.GetGenericParameters();
            InterfaceImplementations = typeDefinition.GetInterfaceImplementations();
            Layout = typeDefinition.GetLayout();
            MethodImplementations = typeDefinition.GetMethodImplementations();
            Methods = typeDefinition.GetMethods();
            NestedTypes = typeDefinition.GetNestedTypes();
            Properties = typeDefinition.GetProperties();
        }

        public override TypeDefinitionHandle Write(MetadataDesigner designer)
        {
            MetadataBuilder builder = designer.Builder;
            TypeDefinitionHandle typeDefinitionHandle = builder.AddTypeDefinition(
                attributes: Attributes,
                @namespace: Namespace == null ? default : builder.GetOrAddString(Namespace),
                name: Name == null ? default : builder.GetOrAddString(Name),
                baseType: BaseType,
                fieldList: MetadataTokens.FieldDefinitionHandle(designer.FieldDefinitionNumber.First),
                methodList: MetadataTokens.MethodDefinitionHandle(designer.MethodDefinitionNumber.First));
            designer.FieldDefinitionNumber.Next();
            designer.MethodDefinitionNumber.Next();
            if (designer.PropertyDefinitionNumber.Current != designer.PropertyDefinitionNumber.First)
            {
                builder.AddPropertyMap(
                    typeDefinitionHandle,
                    MetadataTokens.PropertyDefinitionHandle(designer.PropertyDefinitionNumber.First));
                designer.PropertyDefinitionNumber.Next();
            }
            if (designer.EventDefinitionNumber.Current != designer.EventDefinitionNumber.First)
            {
                builder.AddEventMap(
                    typeDefinitionHandle,
                    MetadataTokens.EventDefinitionHandle(designer.EventDefinitionNumber.First));
                designer.EventDefinitionNumber.Next();
            }
            return typeDefinitionHandle;
        }
    }
}